@fieldwangai/agentflow 0.1.75 → 0.1.77

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -297,6 +297,10 @@
297
297
  "displayName": "WeCom App Markdown",
298
298
  "description": "Send a Markdown message to users through a WeCom enterprise application"
299
299
  },
300
+ "tool_display_share_link": {
301
+ "displayName": "Display Share Link",
302
+ "description": "Publish upstream Display nodes as a shareable link and output the URL"
303
+ },
300
304
  "tool_nodejs": {
301
305
  "displayName": "Node.js Script",
302
306
  "description": "Execute Node.js script, success determined by exit code, stdout as result"
@@ -309,6 +313,10 @@
309
313
  "displayName": "Get Env",
310
314
  "description": "Get environment variable value"
311
315
  },
316
+ "tool_set_run_env": {
317
+ "displayName": "Set Run Env",
318
+ "description": "Set temporary environment variables for downstream nodes in the current run"
319
+ },
312
320
  "tool_save_key": {
313
321
  "displayName": "Save Key",
314
322
  "description": "Save key-value pair to global storage"
@@ -353,6 +361,10 @@
353
361
  "displayName": "File",
354
362
  "description": "Provide file path or content directly, value will be passed to downstream as-is"
355
363
  },
364
+ "provide_password": {
365
+ "displayName": "Password",
366
+ "description": "Provide a password or secret text value without showing it on the node card"
367
+ },
356
368
  "provide_bool": {
357
369
  "displayName": "Boolean",
358
370
  "description": "Provide a true/false boolean value directly to downstream bool pins"
@@ -297,6 +297,10 @@
297
297
  "displayName": "企业微信应用 Markdown",
298
298
  "description": "通过企业微信应用消息接口发送 Markdown 给用户"
299
299
  },
300
+ "tool_display_share_link": {
301
+ "displayName": "展示分享链接",
302
+ "description": "将上游 Display 节点发布为可分享链接,并输出 URL"
303
+ },
300
304
  "tool_nodejs": {
301
305
  "displayName": "Node.js 脚本",
302
306
  "description": "执行 Node.js 脚本,以 exit code 判断成败,stdout 作为结果"
@@ -309,6 +313,10 @@
309
313
  "displayName": "获取环境变量",
310
314
  "description": "获取环境变量值"
311
315
  },
316
+ "tool_set_run_env": {
317
+ "displayName": "设置 Run Env",
318
+ "description": "为当前运行设置临时环境变量,只影响后续节点"
319
+ },
312
320
  "tool_save_key": {
313
321
  "displayName": "保存键值",
314
322
  "description": "保存键值对到全局存储"
@@ -353,6 +361,10 @@
353
361
  "displayName": "文件",
354
362
  "description": "直接提供文件路径或内容,value 会原样供下游引用"
355
363
  },
364
+ "provide_password": {
365
+ "displayName": "密码",
366
+ "description": "直接提供密码或密钥文本,默认不在节点卡片上展示"
367
+ },
356
368
  "provide_bool": {
357
369
  "displayName": "布尔",
358
370
  "description": "直接提供 true/false 布尔值,value 会供下游 bool 引脚引用"
package/bin/lib/paths.mjs CHANGED
@@ -332,11 +332,14 @@ export const LOCAL_ONLY_DEFINITION_IDS = new Set([
332
332
  "tool_gitlab_create_mr",
333
333
  "tool_wecom_send_group_markdown",
334
334
  "tool_wecom_send_app_markdown",
335
+ "tool_display_share_link",
336
+ "tool_set_run_env",
335
337
  "tool_print",
336
338
  "tool_user_check",
337
339
  "tool_user_ask",
338
340
  "provide_str",
339
341
  "provide_file",
342
+ "provide_password",
340
343
  "provide_bool",
341
344
  ]);
342
345
 
@@ -347,5 +350,6 @@ export const LOCAL_ONLY_TERMINAL_SUCCESS_IDS = new Set([
347
350
  "tool_print",
348
351
  "provide_str",
349
352
  "provide_file",
353
+ "provide_password",
350
354
  "provide_bool",
351
355
  ]);
@@ -66,6 +66,7 @@ import {
66
66
  import { clearSkillRegistryCache } from "./skill-registry.mjs";
67
67
  import { COMPOSER_NODE_SPEC_FILENAME } from "./composer-planner.mjs";
68
68
  import { listRecentRunsFromDisk } from "./recent-runs.mjs";
69
+ import { parseBool } from "../pipeline/parse-bool.mjs";
69
70
  import {
70
71
  unzipAndNormalizePipelineZip,
71
72
  validateImportedFlowYaml,
@@ -1249,8 +1250,11 @@ function sanitizeWorkspaceUploadName(filename) {
1249
1250
  .replace(/[^a-zA-Z0-9._-]+/g, "-")
1250
1251
  .replace(/^-+|-+$/g, "")
1251
1252
  .slice(0, 80) || "image";
1252
- const ext = String(parsed.ext || "").toLowerCase();
1253
- return `${stem}${WORKSPACE_IMAGE_EXTS.has(ext) ? ext : ".png"}`;
1253
+ const ext = String(parsed.ext || "")
1254
+ .toLowerCase()
1255
+ .replace(/[^a-z0-9.]+/g, "")
1256
+ .slice(0, 24);
1257
+ return `${stem}${ext}`;
1254
1258
  }
1255
1259
 
1256
1260
  function uniqueWorkspaceRelPath(workspaceRoot, relPath) {
@@ -1434,8 +1438,6 @@ function readWorkspaceFilesRecursive(dir, root, depth = 0, maxDepth = 3, budget
1434
1438
  budget.count++;
1435
1439
  } else if (entry.isFile()) {
1436
1440
  if (WORKSPACE_FILE_SKIP_FILES.has(entry.name)) continue;
1437
- const ext = path.extname(entry.name).toLowerCase();
1438
- if (!WORKSPACE_TEXT_EXTS.has(ext) && !WORKSPACE_IMAGE_EXTS.has(ext)) continue;
1439
1441
  let size = 0;
1440
1442
  try { size = fs.statSync(abs).size; } catch {}
1441
1443
  budget.count++;
@@ -1964,6 +1966,102 @@ function normalizeDisplayShareNodeIds(ids, graph) {
1964
1966
  return out;
1965
1967
  }
1966
1968
 
1969
+ function normalizeDisplayShareLayout(layout, fallback = "canvas") {
1970
+ const text = String(layout || "").trim();
1971
+ return ["canvas", "gallery", "slides", "document", "single"].includes(text) ? text : fallback;
1972
+ }
1973
+
1974
+ function createDisplayShareRecord({ userId, flowId, flowSource, archived, title, layout, nodeIds }) {
1975
+ const shares = readDisplayShares();
1976
+ let id = createDisplayShareId();
1977
+ while (shares[id]) id = createDisplayShareId();
1978
+ const nowDate = new Date();
1979
+ const now = nowDate.toISOString();
1980
+ const share = {
1981
+ id,
1982
+ userId: String(userId || ""),
1983
+ flowId: String(flowId || ""),
1984
+ flowSource: String(flowSource || "user"),
1985
+ archived: archived === true,
1986
+ title: String(title || "").trim() || "AgentFlow Display",
1987
+ layout: normalizeDisplayShareLayout(layout, "canvas"),
1988
+ nodeIds: Array.isArray(nodeIds) ? nodeIds : [],
1989
+ createdAt: now,
1990
+ updatedAt: now,
1991
+ expiresAt: displayShareExpiresAt(nowDate),
1992
+ };
1993
+ shares[id] = share;
1994
+ writeDisplayShares(shares);
1995
+ return share;
1996
+ }
1997
+
1998
+ function parseDisplayShareNodeIdInput(value) {
1999
+ return String(value || "")
2000
+ .split(/[\s,,]+/g)
2001
+ .map((id) => id.trim())
2002
+ .filter(Boolean);
2003
+ }
2004
+
2005
+ function inferUpstreamDisplayNodeIds(graph, nodeId) {
2006
+ const instances = graph?.instances && typeof graph.instances === "object" ? graph.instances : {};
2007
+ const edges = Array.isArray(graph?.edges) ? graph.edges : [];
2008
+ const ids = [];
2009
+ const seen = new Set();
2010
+ for (const edge of edges) {
2011
+ if (String(edge?.target || "") !== String(nodeId || "")) continue;
2012
+ const sourceId = String(edge?.source || "").trim();
2013
+ if (!sourceId || seen.has(sourceId)) continue;
2014
+ if (!workspaceDisplayKind(instances[sourceId]?.definitionId)) continue;
2015
+ seen.add(sourceId);
2016
+ ids.push(sourceId);
2017
+ }
2018
+ return ids;
2019
+ }
2020
+
2021
+ function displayShareOutputUrl(shareId, baseUrl = "") {
2022
+ const pathPart = `/display/${encodeURIComponent(String(shareId || ""))}`;
2023
+ const base = String(baseUrl || "").trim();
2024
+ if (!base) return pathPart;
2025
+ try {
2026
+ return new URL(pathPart, base.endsWith("/") ? base : `${base}/`).href;
2027
+ } catch {
2028
+ return pathPart;
2029
+ }
2030
+ }
2031
+
2032
+ function normalizeRunEnvKey(key) {
2033
+ const text = String(key || "").trim();
2034
+ return /^[A-Za-z_][A-Za-z0-9_]*$/.test(text) ? text : "";
2035
+ }
2036
+
2037
+ function parseRunEnvAssignments(raw = "") {
2038
+ const text = String(raw || "").trim();
2039
+ if (!text) return {};
2040
+ if (text.startsWith("{")) {
2041
+ const parsed = JSON.parse(text);
2042
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) throw new Error("Run Env JSON must be an object");
2043
+ const out = {};
2044
+ for (const [key, value] of Object.entries(parsed)) {
2045
+ const normalizedKey = normalizeRunEnvKey(key);
2046
+ if (!normalizedKey) throw new Error(`Invalid env key: ${key}`);
2047
+ out[normalizedKey] = String(value ?? "");
2048
+ }
2049
+ return out;
2050
+ }
2051
+ const out = {};
2052
+ for (const line of text.split(/\r?\n/g)) {
2053
+ const trimmed = line.trim();
2054
+ if (!trimmed || trimmed.startsWith("#")) continue;
2055
+ const normalized = trimmed.replace(/^export\s+/i, "");
2056
+ const eq = normalized.indexOf("=");
2057
+ if (eq <= 0) throw new Error(`Invalid env assignment: ${trimmed}`);
2058
+ const key = normalizeRunEnvKey(normalized.slice(0, eq));
2059
+ if (!key) throw new Error(`Invalid env key: ${normalized.slice(0, eq).trim()}`);
2060
+ out[key] = normalized.slice(eq + 1);
2061
+ }
2062
+ return out;
2063
+ }
2064
+
1967
2065
  function publicDisplayPayloadFromShare(root, share) {
1968
2066
  const scoped = resolveWorkspaceScopeRoot(root, {
1969
2067
  flowId: share.flowId || "",
@@ -3324,6 +3422,13 @@ function workspaceIsControlEdge(graph, edge) {
3324
3422
  workspaceIsControlInputSlot(workspaceTargetSlotForEdge(graph, edge));
3325
3423
  }
3326
3424
 
3425
+ function workspaceControlIfBranchToSourceHandle(branch) {
3426
+ const text = String(branch || "").trim().toLowerCase();
3427
+ if (text === "true" || text === "next1") return "output-0";
3428
+ if (text === "false" || text === "next2") return "output-1";
3429
+ return null;
3430
+ }
3431
+
3327
3432
  function workspaceNeedsUpstreamExecutionForEdge(graph, edge, scopedRoot = "") {
3328
3433
  if (workspaceIsControlEdge(graph, edge)) return true;
3329
3434
  return !workspaceEdgeHasCachedOutput(graph, edge, scopedRoot);
@@ -3341,7 +3446,7 @@ function workspaceEdgeHasCachedOutput(graph, edge, scopedRoot = "") {
3341
3446
  }
3342
3447
  const defId = String(source.definitionId || "");
3343
3448
  if (workspaceDisplayKind(defId) && String(source.body || "").trim()) return true;
3344
- if (defId === "provide_str" || defId === "provide_bool" || defId === "provide_file") {
3449
+ if (defId === "provide_str" || defId === "provide_bool" || defId === "provide_file" || defId === "provide_password") {
3345
3450
  return Boolean(String(workspaceInstanceText(source) || "").trim());
3346
3451
  }
3347
3452
  return false;
@@ -4396,6 +4501,7 @@ async function workspaceRunToolNodejsScript({
4396
4501
  inputValues,
4397
4502
  runPackage,
4398
4503
  userCtx,
4504
+ envOverlay = {},
4399
4505
  emit,
4400
4506
  }) {
4401
4507
  const scriptRef = String(instance?.scriptRef || "").trim();
@@ -4431,6 +4537,7 @@ async function workspaceRunToolNodejsScript({
4431
4537
  emit?.({ type: "status", line: `Run script: ${scriptRef || command.slice(0, 120)}` });
4432
4538
 
4433
4539
  const env = runtimeEnvForUser(userCtx, {
4540
+ ...envOverlay,
4434
4541
  AGENTFLOW_WORKSPACE_ROOT: path.resolve(scopedRoot),
4435
4542
  AGENTFLOW_NODE_RUN_DIR: runPackage.nodeRunDir,
4436
4543
  AGENTFLOW_NODE_TMP_DIR: runPackage.nodeTmpDir,
@@ -4542,8 +4649,34 @@ async function runWorkspaceGraph(root, scopedRoot, payload, userCtx = {}, opts =
4542
4649
  };
4543
4650
  let cwd = scopedRoot;
4544
4651
  const modelKey = typeof payload?.model === "string" ? payload.model.trim() : "";
4652
+ const runEnv = {};
4653
+ const runtimeEnv = (extra = {}) => runtimeEnvForUser(userCtx, { ...runEnv, ...(extra || {}) });
4545
4654
  const autoCleanupWorktrees = [];
4546
4655
  const runTmpRoot = workspaceCreateRunTmpRoot(scopedRoot, runNodeId);
4656
+ const controlBranches = new Map();
4657
+ const skippedNodes = new Set();
4658
+ const incomingControlEdgesByTarget = new Map();
4659
+ for (const edge of Array.isArray(graph?.edges) ? graph.edges : []) {
4660
+ const target = String(edge?.target || "");
4661
+ if (!target || !workspaceIsControlEdge(graph, edge)) continue;
4662
+ if (!incomingControlEdgesByTarget.has(target)) incomingControlEdgesByTarget.set(target, []);
4663
+ incomingControlEdgesByTarget.get(target).push(edge);
4664
+ }
4665
+ const skipReasonForNode = (nodeId) => {
4666
+ for (const edge of incomingControlEdgesByTarget.get(nodeId) || []) {
4667
+ const sourceId = String(edge?.source || "");
4668
+ if (!sourceId) continue;
4669
+ if (skippedNodes.has(sourceId)) return `上游 ${sourceId} 已被分支跳过`;
4670
+ const sourceDefId = String(graph.instances?.[sourceId]?.definitionId || "");
4671
+ if (sourceDefId !== "control_if" || !controlBranches.has(sourceId)) continue;
4672
+ const expectedHandle = workspaceControlIfBranchToSourceHandle(controlBranches.get(sourceId));
4673
+ const actualHandle = String(edge?.sourceHandle || "output-0");
4674
+ if (expectedHandle && actualHandle !== expectedHandle) {
4675
+ return `control_if ${sourceId} 分支为 ${controlBranches.get(sourceId)},跳过 ${actualHandle}`;
4676
+ }
4677
+ }
4678
+ return "";
4679
+ };
4547
4680
 
4548
4681
  try {
4549
4682
  for (const nodeId of order) {
@@ -4551,6 +4684,13 @@ async function runWorkspaceGraph(root, scopedRoot, payload, userCtx = {}, opts =
4551
4684
  const instance = graph.instances[nodeId];
4552
4685
  if (!instance) continue;
4553
4686
  const defId = String(instance.definitionId || "");
4687
+ const skipReason = skipReasonForNode(nodeId);
4688
+ if (skipReason) {
4689
+ skippedNodes.add(nodeId);
4690
+ emit({ type: "status", nodeId, line: `Skipped: ${skipReason}` });
4691
+ emit({ type: "node-done", nodeId, definitionId: defId, skipped: true });
4692
+ continue;
4693
+ }
4554
4694
  emit({ type: "node-start", nodeId, definitionId: defId });
4555
4695
 
4556
4696
  if (defId === "workspace_run" || defId === "workspace_scheduled_run") {
@@ -4606,7 +4746,24 @@ async function runWorkspaceGraph(root, scopedRoot, payload, userCtx = {}, opts =
4606
4746
  continue;
4607
4747
  }
4608
4748
 
4609
- if (defId === "provide_str") {
4749
+ if (defId === "control_if") {
4750
+ const inputValues = workspaceInputValues(graph, nodeId, outputs, scopedRoot);
4751
+ const boolSlot = (Array.isArray(instance.input) ? instance.input : [])
4752
+ .find((slot) => String(slot?.type || "").trim().toLowerCase() === "bool");
4753
+ const boolSlotName = String(boolSlot?.name || "").trim();
4754
+ const rawValue = boolSlotName && Object.prototype.hasOwnProperty.call(inputValues, boolSlotName)
4755
+ ? inputValues[boolSlotName]
4756
+ : workspaceSlotValue(boolSlot);
4757
+ const boolValue = parseBool(rawValue);
4758
+ const branch = boolValue ? "true" : "false";
4759
+ controlBranches.set(nodeId, branch);
4760
+ outputs.set(nodeId, branch);
4761
+ emit({ type: "status", nodeId, line: `control_if branch: ${branch}` });
4762
+ emit({ type: "node-done", nodeId, definitionId: defId, branch });
4763
+ continue;
4764
+ }
4765
+
4766
+ if (defId === "provide_str" || defId === "provide_password") {
4610
4767
  const content = workspaceInstanceText(instance);
4611
4768
  outputs.set(nodeId, content);
4612
4769
  emit({ type: "node-done", nodeId, definitionId: defId });
@@ -4633,6 +4790,26 @@ async function runWorkspaceGraph(root, scopedRoot, payload, userCtx = {}, opts =
4633
4790
  continue;
4634
4791
  }
4635
4792
 
4793
+ if (defId === "tool_set_run_env") {
4794
+ const inputValues = workspaceInputValues(graph, nodeId, outputs, scopedRoot);
4795
+ const assignments = {
4796
+ ...parseRunEnvAssignments(inputValues.variables || workspaceSlotValue(workspaceSlotByName(instance, "variables"))),
4797
+ };
4798
+ const key = normalizeRunEnvKey(inputValues.key || workspaceSlotValue(workspaceSlotByName(instance, "key")));
4799
+ if (key) assignments[key] = String(inputValues.value ?? workspaceSlotValue(workspaceSlotByName(instance, "value")) ?? "");
4800
+ const keys = Object.keys(assignments);
4801
+ if (!keys.length) throw new Error("Set Run Env requires key/value or variables");
4802
+ Object.assign(runEnv, assignments);
4803
+ let nextInstance = workspaceSetOutputSlot(instance, "keys", keys.join(", "));
4804
+ nextInstance = workspaceSetOutputSlot(nextInstance, "count", String(keys.length));
4805
+ graph.instances[nodeId] = nextInstance;
4806
+ outputs.set(nodeId, keys.join(", "));
4807
+ emit({ type: "status", nodeId, line: `Set run env: ${keys.join(", ")}`, envKeys: keys });
4808
+ emit({ type: "graph", nodeId, graph });
4809
+ emit({ type: "node-done", nodeId, definitionId: defId });
4810
+ continue;
4811
+ }
4812
+
4636
4813
  if (defId === "control_cd_workspace") {
4637
4814
  const inputText = workspaceUpstreamText(graph, nodeId, outputs, scopedRoot);
4638
4815
  const inputSlots = Array.isArray(instance.input) ? instance.input : [];
@@ -4829,7 +5006,7 @@ async function runWorkspaceGraph(root, scopedRoot, payload, userCtx = {}, opts =
4829
5006
  gitlabApiBase: workspaceSlotValue(workspaceSlotByName(instance, "gitlabApiBase")),
4830
5007
  removeSourceBranch: workspaceSlotValue(workspaceSlotByName(instance, "removeSourceBranch")),
4831
5008
  squash: workspaceSlotValue(workspaceSlotByName(instance, "squash")),
4832
- }, runtimeEnvForUser(userCtx));
5009
+ }, runtimeEnv());
4833
5010
  let nextInstance = workspaceSetOutputSlot(instance, "mrUrl", result.mrUrl);
4834
5011
  nextInstance = workspaceSetOutputSlot(nextInstance, "created", result.created ? "true" : "false");
4835
5012
  nextInstance = workspaceSetOutputSlot(nextInstance, "mrIid", result.mrIid ?? "");
@@ -4856,12 +5033,12 @@ async function runWorkspaceGraph(root, scopedRoot, payload, userCtx = {}, opts =
4856
5033
  corpSecret: inputValues.corpSecret || workspaceSlotValue(workspaceSlotByName(instance, "corpSecret")),
4857
5034
  agentId: inputValues.agentId || workspaceSlotValue(workspaceSlotByName(instance, "agentId")),
4858
5035
  accessToken: inputValues.accessToken || workspaceSlotValue(workspaceSlotByName(instance, "accessToken")),
4859
- }, runtimeEnvForUser(userCtx))
5036
+ }, runtimeEnv())
4860
5037
  : await sendWecomGroupMarkdown({
4861
5038
  markdown,
4862
5039
  webhookUrl: inputValues.webhookUrl || workspaceSlotValue(workspaceSlotByName(instance, "webhookUrl")),
4863
5040
  webhookKey: inputValues.webhookKey || workspaceSlotValue(workspaceSlotByName(instance, "webhookKey")),
4864
- }, runtimeEnvForUser(userCtx));
5041
+ }, runtimeEnv());
4865
5042
  let nextInstance = workspaceSetOutputSlot(instance, "sent", "true");
4866
5043
  nextInstance = workspaceSetOutputSlot(nextInstance, "message", result.message);
4867
5044
  nextInstance = workspaceSetOutputSlot(nextInstance, "response", JSON.stringify(result.response || {}));
@@ -4872,6 +5049,52 @@ async function runWorkspaceGraph(root, scopedRoot, payload, userCtx = {}, opts =
4872
5049
  continue;
4873
5050
  }
4874
5051
 
5052
+ if (defId === "tool_display_share_link") {
5053
+ const inputValues = workspaceInputValues(graph, nodeId, outputs, scopedRoot);
5054
+ const explicitNodeIds = parseDisplayShareNodeIdInput(inputValues.nodeIds || workspaceSlotValue(workspaceSlotByName(instance, "nodeIds")));
5055
+ const inferredNodeIds = explicitNodeIds.length ? explicitNodeIds : inferUpstreamDisplayNodeIds(graph, nodeId);
5056
+ const nodeIds = normalizeDisplayShareNodeIds(inferredNodeIds, graph);
5057
+ if (nodeIds.length === 0) throw new Error("Display Share Link requires at least one connected display node or nodeIds input");
5058
+
5059
+ const layout = normalizeDisplayShareLayout(inputValues.layout || workspaceSlotValue(workspaceSlotByName(instance, "layout")), "single");
5060
+ const title = inputValues.title || workspaceSlotValue(workspaceSlotByName(instance, "title"));
5061
+ const env = runtimeEnv();
5062
+ const baseUrl = inputValues.baseUrl ||
5063
+ workspaceSlotValue(workspaceSlotByName(instance, "baseUrl")) ||
5064
+ env.AGENTFLOW_PUBLIC_BASE_URL ||
5065
+ env.AGENTFLOW_BASE_URL ||
5066
+ env.PUBLIC_BASE_URL ||
5067
+ "";
5068
+
5069
+ const graphPath = workspaceGraphPath(scopedRoot);
5070
+ try {
5071
+ const currentGraph = readWorkspaceGraph(scopedRoot).graph;
5072
+ const mergedGraph = mergeWorkspaceRunGraph(currentGraph, graph, new Set([nodeId, ...nodeIds]));
5073
+ fs.writeFileSync(graphPath, JSON.stringify(mergedGraph, null, 2) + "\n", "utf-8");
5074
+ } catch (e) {
5075
+ emit({ type: "natural", kind: "warning", text: `保存分享展示内容失败:${(e && e.message) || String(e)}` });
5076
+ }
5077
+
5078
+ const share = createDisplayShareRecord({
5079
+ userId: userCtx.userId,
5080
+ flowId: payload.flowId || "",
5081
+ flowSource: payload.flowSource || "user",
5082
+ archived: payload.archived === true || payload.flowArchived === true,
5083
+ title,
5084
+ layout,
5085
+ nodeIds,
5086
+ });
5087
+ const url = displayShareOutputUrl(share.id, baseUrl);
5088
+ let nextInstance = workspaceSetOutputSlot(instance, "url", url);
5089
+ nextInstance = workspaceSetOutputSlot(nextInstance, "shareId", share.id);
5090
+ nextInstance = workspaceSetOutputSlot(nextInstance, "expiresAt", share.expiresAt);
5091
+ graph.instances[nodeId] = nextInstance;
5092
+ outputs.set(nodeId, url);
5093
+ emit({ type: "graph", nodeId, graph, displayNodeIds: nodeIds });
5094
+ emit({ type: "node-done", nodeId, definitionId: defId });
5095
+ continue;
5096
+ }
5097
+
4875
5098
  if (defId === "tool_nodejs") {
4876
5099
  const prepareStartedAt = Date.now();
4877
5100
  const inputValues = workspaceInputValues(graph, nodeId, outputs, scopedRoot);
@@ -4893,6 +5116,7 @@ async function runWorkspaceGraph(root, scopedRoot, payload, userCtx = {}, opts =
4893
5116
  inputValues: runtimeInputValues,
4894
5117
  runPackage,
4895
5118
  userCtx,
5119
+ envOverlay: runEnv,
4896
5120
  emit: (event) => emit({ ...event, nodeId }),
4897
5121
  });
4898
5122
  const normalizedAgentOutput = workspacePublishAgentOutputFiles(workspaceStructuredAgentOutput(content), runPackage);
@@ -4974,12 +5198,12 @@ async function runWorkspaceGraph(root, scopedRoot, payload, userCtx = {}, opts =
4974
5198
  prompt,
4975
5199
  modelKey,
4976
5200
  agentflowUserId: userCtx.userId || "",
4977
- extraEnv: {
5201
+ extraEnv: runtimeEnv({
4978
5202
  AGENTFLOW_WORKSPACE_TMP_ROOT: runTmpRoot,
4979
5203
  AGENTFLOW_NODE_RUN_DIR: runPackage.nodeRunDir,
4980
5204
  AGENTFLOW_NODE_TMP_DIR: runPackage.nodeTmpDir,
4981
5205
  AGENTFLOW_OUTPUTS_DIR: runPackage.outputsDir,
4982
- },
5206
+ }),
4983
5207
  onStreamEvent: (ev) => {
4984
5208
  if (!firstAgentEventSeen) {
4985
5209
  firstAgentEventSeen = true;
@@ -6363,27 +6587,16 @@ export function startUiServer({
6363
6587
  json(res, 400, { error: "请选择至少一个 display 节点" });
6364
6588
  return;
6365
6589
  }
6366
- const shares = readDisplayShares();
6367
- let id = createDisplayShareId();
6368
- while (shares[id]) id = createDisplayShareId();
6369
- const nowDate = new Date();
6370
- const now = nowDate.toISOString();
6371
- const share = {
6372
- id,
6590
+ const share = createDisplayShareRecord({
6373
6591
  userId: authUser.userId,
6374
6592
  flowId: scoped.flowId || "",
6375
6593
  flowSource: scoped.flowSource || "user",
6376
6594
  archived: scoped.archived === true,
6377
- title: String(payload.title || "").trim() || "AgentFlow Display",
6378
- layout: ["canvas", "gallery", "slides", "document", "single"].includes(String(payload.layout || "")) ? String(payload.layout) : "canvas",
6595
+ title: payload.title,
6596
+ layout: payload.layout,
6379
6597
  nodeIds,
6380
- createdAt: now,
6381
- updatedAt: now,
6382
- expiresAt: displayShareExpiresAt(nowDate),
6383
- };
6384
- shares[id] = share;
6385
- writeDisplayShares(shares);
6386
- json(res, 200, { ok: true, share, url: `/display/${encodeURIComponent(id)}` });
6598
+ });
6599
+ json(res, 200, { ok: true, share, url: `/display/${encodeURIComponent(share.id)}` });
6387
6600
  } catch (e) {
6388
6601
  json(res, 500, { error: (e && e.message) || String(e) });
6389
6602
  }
@@ -6897,13 +7110,9 @@ export function startUiServer({
6897
7110
  return;
6898
7111
  }
6899
7112
  const safeName = sanitizeWorkspaceUploadName(parsed.filename);
6900
- const ext = path.extname(safeName).toLowerCase();
6901
- if (!WORKSPACE_IMAGE_EXTS.has(ext) || (parsed.mimeType && !/^image\//i.test(parsed.mimeType))) {
6902
- json(res, 400, { error: "Only image uploads are supported" });
6903
- return;
6904
- }
6905
- const targetDir = String(parsed.fields.dir || "img").trim().replace(/^[/\\]+/, "") || "img";
6906
- const target = uniqueWorkspaceRelPath(scoped.root, path.posix.join(targetDir.replace(/\\/g, "/"), safeName));
7113
+ const targetDir = String(parsed.fields.dir ?? "").trim().replace(/^[/\\]+/, "").replace(/\\/g, "/");
7114
+ const targetRel = targetDir ? path.posix.join(targetDir, safeName) : safeName;
7115
+ const target = uniqueWorkspaceRelPath(scoped.root, targetRel);
6907
7116
  fs.mkdirSync(path.dirname(target.abs), { recursive: true });
6908
7117
  fs.writeFileSync(target.abs, parsed.file);
6909
7118
  json(res, 200, {
@@ -0,0 +1,13 @@
1
+ ---
2
+ # Built-in node: directly provide a hidden password/secret value
3
+ description: Provide a secret text value without showing it on the node card
4
+ displayName: Password
5
+ input: []
6
+ output:
7
+ - type: text
8
+ name: value
9
+ default: ""
10
+ required: true
11
+ showOnNode: true
12
+ ---
13
+ ${USER_PROMPT}
@@ -0,0 +1,46 @@
1
+ ---
2
+ # Built-in node: create share link for Display nodes
3
+ description: Create a public share link for upstream Display nodes
4
+ displayName: Display Share Link
5
+ input:
6
+ - type: node
7
+ name: prev
8
+ default: ""
9
+ - type: text
10
+ name: title
11
+ default: ""
12
+ description: "分享页标题;为空时使用 AgentFlow Display。"
13
+ showOnNode: true
14
+ - type: text
15
+ name: layout
16
+ default: "single"
17
+ description: "分享布局:single、gallery、slides、document、canvas。"
18
+ showOnNode: true
19
+ - type: text
20
+ name: nodeIds
21
+ default: ""
22
+ description: "可选。逗号或空格分隔的 Display 节点 ID;为空时自动使用连接到本节点的上游 Display 节点。"
23
+ showOnNode: false
24
+ - type: text
25
+ name: baseUrl
26
+ default: ""
27
+ description: "可选。输出绝对链接的站点地址,例如 https://agentflow.example.com;为空时输出 /display/<id>。"
28
+ showOnNode: false
29
+ output:
30
+ - type: node
31
+ name: next
32
+ default: ""
33
+ - type: text
34
+ name: url
35
+ default: ""
36
+ showOnNode: true
37
+ - type: text
38
+ name: shareId
39
+ default: ""
40
+ showOnNode: false
41
+ - type: text
42
+ name: expiresAt
43
+ default: ""
44
+ showOnNode: false
45
+ ---
46
+ Create a share link for connected Display nodes.
@@ -0,0 +1,37 @@
1
+ ---
2
+ # Built-in node: set environment variables for the current run only
3
+ description: Set environment variables for downstream nodes in the current workspace run
4
+ displayName: Set Run Env
5
+ input:
6
+ - type: node
7
+ name: prev
8
+ default: ""
9
+ - type: text
10
+ name: key
11
+ default: ""
12
+ description: "单个环境变量名,例如 IMAP_USER。"
13
+ showOnNode: true
14
+ - type: text
15
+ name: value
16
+ default: ""
17
+ description: "单个环境变量值。"
18
+ showOnNode: true
19
+ - type: text
20
+ name: variables
21
+ default: ""
22
+ description: "批量设置,支持多行 KEY=VALUE 或 JSON object。"
23
+ showOnNode: false
24
+ output:
25
+ - type: node
26
+ name: next
27
+ default: ""
28
+ - type: text
29
+ name: keys
30
+ default: ""
31
+ showOnNode: false
32
+ - type: text
33
+ name: count
34
+ default: ""
35
+ showOnNode: false
36
+ ---
37
+ Set environment variables for downstream nodes in this run only.